﻿using System.Collections.Generic;
using System.IO;
using System.Text;

using Uhi.Libraries.Text;
using Uhi.Libraries.Windows.ViewModels;

using Uhi.Applications.FileAid.Model;

namespace Uhi.Applications.FileAid.ViewModel
{
    #region RecordViewModel
    /// <summary>
    /// ファイルの 1 行分のデータを表す ViewModel。
    /// </summary>
    public class RecordViewModel : ViewModelBase
    {
        #region _ctor
        /// <summary>
        /// <see cref="RecordViewModel"/> の新しいインスタンスを生成する。
        /// </summary>
        /// <param name="rec">このインスタンスが表すレコードの <see cref="RecordInfo"/>。</param>
        public RecordViewModel(IRecordInfo rec)
        {
            this.RecordInfo = rec;
            this.Encode = Encoding.Default;
        }
        #endregion

        #region RecordInfo
        /// <summary>
        /// このインスタンスが表すレコードの <see cref="RecordInfo"/> を取得、または設定する。
        /// </summary>
        private IRecordInfo RecordInfo { get; set; }
        #endregion

        #region Index
        /// <summary>
        /// データの行番号を取得する。
        /// </summary>
        /// <remarks>
        /// 先頭の番号は 1。
        /// </remarks>
        public int Index
        {
            get { return this.RecordInfo.Line + 1; }
        }
        #endregion

        #region Encode
        /// <summary>
        /// ファイルのエンコードを取得、または設定する。
        /// </summary>
        private Encoding Encode { get; set; }
        #endregion

        #region Record
        /// <summary>
        /// レコードのデータを取得、または設定する。
        /// </summary>
        public string Record
        {
            get
            {
                var rec = this.RecordInfo.Record;
                return this.Encode.GetString(rec, 0, rec.Length);
            }
            set
            {
                var str = value.PadRight(this.RecordInfo.Length).Substring(0, this.RecordInfo.Length);
                this.RecordInfo.Record = this.Encode.GetBytes(str);

                this.RecordInfo.IsEdited = true;

                this.OnPropertyChanged("Record");
                this.OnPropertyChanged("HexRecord");
            }
        }
        #endregion

        #region HexRecord
        /// <summary>
        /// レコードの 16 進表示のデータを取得、または設定する。
        /// </summary>
        public string HexRecord
        {
            get { return this.RecordInfo.Record.ByteToHex(); }
            set
            {
                var len = this.RecordInfo.Length * 2;
                var rec = value.PadRight(len, '0').Substring(0, len).HexToByte();

                if (this.RecordInfo.Record.Equals(rec))
                    return;

                this.RecordInfo.IsEdited = true;

                this.RecordInfo.Record = rec;
                this.OnPropertyChanged("Record");
                this.OnPropertyChanged("HexRecord");
            }
        }
        #endregion

        #region CaretIndex

        /// <summary>
        /// テキストボックスのカーソル位置。
        /// </summary>
        private int caretIndex;

        /// <summary>
        /// テキストボックスのカーソル位置を取得、または設定する。
        /// </summary>
        public int CaretIndex
        {
            get { return this.caretIndex; }
            set
            {
                if (this.caretIndex == value)
                    return;

                this.caretIndex = value;
                this.OnPropertyChanged("CaretIndex");
            }
        }

        #endregion

        #region HexCaretIndex

        /// <summary>
        /// 16 進数表記のテキストボックスのカーソル位置。
        /// </summary>
        private int hexCaretIndex;

        /// <summary>
        /// 16 進数表記のテキストボックスのカーソル位置を取得、または設定する。
        /// </summary>
        public int HexCaretIndex
        {
            get { return this.hexCaretIndex; }
            set
            {
                if (this.hexCaretIndex == value)
                    return;

                this.hexCaretIndex = value;
                this.OnPropertyChanged("HexCaretIndex");
            }
        }

        #endregion

        #region GetRecords

        /// <summary>
        /// 指定したファイルのレコードを表す <see cref="RecordViewModel"/> を取得する。
        /// </summary>
        /// <param name="filename">ファイルの path。</param>
        /// <param name="length">レコード長。</param>
        /// <param name="encode">ファイルの文字コード。</param>
        /// <returns>指定したファイルのレコードを表す <see cref="RecordViewModel"/>。</returns>
        public static RecordViewModel[] GetRecords(string filename, int length, string encode)
        {
            if (!File.Exists(filename))
                return null;

            var recs = new List<RecordViewModel>();

            if (length <= 0)
                return recs.ToArray();

            var fileSize = new FileInfo(filename).Length;
            var enc = Encoding.GetEncoding(encode);
            for (int i = 0; i * length < fileSize; i++)
                recs.Add(new RecordViewModel(new RecordInfo(filename, length * i, length, i)) { Encode = enc });
            
            return recs.ToArray();
        }

        /// <summary>
        /// 指定したファイルのレコードを表す <see cref="RecordViewModel"/> を取得する。
        /// </summary>
        /// <param name="filename">ファイルの path。</param>
        /// <param name="length">レコード長。</param>
        /// <param name="encode">ファイルの文字コード。</param>
        /// <param name="lineBreak">改行コード。</param>
        /// <returns>指定したファイルのレコードを表す <see cref="RecordViewModel"/>。</returns>
        public static RecordViewModel[] GetRecords(string filename, int length, string encode, string lineBreak)
        {
            if (!File.Exists(filename))
                return null;

            var recs = new List<RecordViewModel>();

            if (length <= 0)
                return recs.ToArray();

            var fileSize = new FileInfo(filename).Length;
            var enc = Encoding.GetEncoding(encode);
            var offset = 0;
            var line = 0;
            var buf = new byte[length];
            using (var stream = new FileStream(filename, FileMode.Open))
            {
                while (offset <= fileSize)
                {
                    stream.Seek(offset, SeekOrigin.Begin);
                    if (stream.Read(buf, 0, length) <= 0)
                        break;
                    var text = enc.GetString(buf);
                    var idx = text.IndexOf(lineBreak);
                    var len = idx < 0 ? length : idx + lineBreak.Length;
                    var rec = new RecordInfo(filename, offset, len, line);
                    recs.Add(new RecordViewModel(rec) { Encode = enc });
                    offset += len;
                    line++;
                }
            }

            return recs.ToArray();
        }

        #endregion

        #region Save

        /// <summary>
        /// ファイルの変更内容を保存する。
        /// </summary>
        public void Save()
        {
            this.RecordInfo.Save();
        }

        /// <summary>
        /// <see cref="Record"/> を指定したファイルに保存する。
        /// </summary>
        /// <param name="path">保存するファイルの path。</param>
        public void SaveAs(string path)
        {
            this.RecordInfo.SaveAs(path);
        }

        #endregion
    }
    #endregion
}
